iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0
Software Development

30 天資料結構修行:從零開始理解資料結構系列 第 4

Day-4 陣列的幕後世界:記憶體與指標

  • 分享至 

  • xImage
  •  

上一篇已經學會如何宣告陣列、使用索引存取元素,並搭配迴圈處理全部資料。這一篇要繼續往下理解:陣列為什麼能透過索引快速找到元素?陣列名稱又為什麼和指標有關?

這些語法表面上看起來不太一樣,幕後其實都和同一件事有關:陣列元素在記憶體中的排列方式。

陣列使用連續的記憶體空間

陣列不只是把資料放在同一個名稱底下,它還會讓所有元素在記憶體中一個接一個排列,中間不會穿插其他資料。這就是所謂的連續記憶體空間

可以寫一支程式,實際印出陣列中每個元素的數值與記憶體位址:

#include <stdio.h>

int main(void) {
    int scores[5] = {80, 90, 75, 60, 85};

    for (int i = 0; i < 5; i++) {
        printf("scores[%d] = %d,記憶體位址:%p\n",
               i,
               scores[i],
               (void *)&scores[i]);
    }

    return 0;
}

%pprintf() 用來輸出記憶體位址的格式,(void *) 則把位址轉成 %p 預期接收的通用指標型態。

某一次執行的輸出如下:

scores[0] = 80,記憶體位址:0x16dafa5a0
scores[1] = 90,記憶體位址:0x16dafa5a4
scores[2] = 75,記憶體位址:0x16dafa5a8
scores[3] = 60,記憶體位址:0x16dafa5ac
scores[4] = 85,記憶體位址:0x16dafa5b0

記憶體位址每次執行時可能不同,但這次的結果清楚顯示,位址尾數依序是 a0a4a8acb0,每次都增加 4 bytes。這表示這個環境中的 int 占用 4 bytes,也驗證了陣列元素會連續存放。

因為排列方式很規律,電腦可以利用下面的概念直接算出某個元素的位置:

元素位址 = 陣列起始位址 + 索引 × 每個元素的大小

所以 scores[3] 的位址
= 0x16dafa5a0 + 3 × 4 bytes
= 0x16dafa5a0 + 12 bytes
= 0x16dafa5ac

所以,無論要讀取第一個元素還是第一千個元素,只要知道索引,就可以直接找到它,不需要從頭逐一尋找。使用索引存取陣列元素的時間複雜度是 O(1)

陣列名稱和指標有什麼關係?

陣列與指標是 C 語言中關係非常密切的兩個觀念。以下面的陣列為例:

int scores[5] = {80, 90, 75, 60, 85};

在大多數運算式中,陣列名稱 scores 會自動轉換成指向第一個元素的指標,因此下面的比較結果會成立:

scores == &scores[0]

&取址運算子,所以 &scores[0] 代表「取得第一個元素的記憶體位址」。

因此,下面兩種寫法會取得相同的元素:

scores[2]
*(scores + 2)

這裡的 *解參照運算子,用來透過記憶體位址取出裡面的數值。因此兩種寫法得到的都是 75

scores[i] 等價於 *(scores + i)

https://ithelp.ithome.com.tw/upload/images/20260919/20183409y3SgdkfoC0.png

這裡的 scores + 2 不是把記憶體位址單純加上 2 bytes。因為 scores 指向 int,指標加上 2 時,會往後跨過兩個 int 元素的距離。假設一個 int 占用 4 bytes,實際上就是往後移動 8 bytes,到達 scores[2] 的位置。

可以先把陣列名稱想成:它固定在第一個元素的位置。我們不能讓 scores 自己往後移:

scores++; // 錯誤

不過,陣列裡面的資料還是可以修改:

scores[0] = 100; // 正確

所以,陣列不是指標;只是在很多情況下,陣列名稱會被當成第一個元素的位址使用。現在先記住這點就可以了。

將陣列傳入函式

如果要讓函式處理陣列,可以把陣列名稱傳給函式:

#include <stdio.h>

void printScores(int scores[], int length) {
    for (int i = 0; i < length; i++) {
        printf("%d ", scores[i]);
    }
    printf("\n");
}

int main(void) {
    int scores[5] = {80, 90, 75, 60, 85};

    printScores(scores, 5);

    return 0;
}

在函式的參數中,int scores[] 會被視為 int *scores,也就是指向第一個元素的指標。函式無法只靠這個指標知道陣列共有幾個元素,因此通常還要另外傳入陣列長度,例如上面的 length

小結

陣列的元素會連續存放在記憶體中,因此電腦可以根據起始位址、索引與元素大小,直接計算出指定元素的位置。這也是陣列能在 O(1) 時間內存取指定元素的原因。

今日重點:

陣列的元素會連續存放在記憶體中。
相鄰元素的位址差距,等於一個元素所占的空間。
取址運算子 & 用來取得位址,解參照運算子 * 用來透過位址取值。
陣列名稱在大多數運算式中會轉換成第一個元素的位址,但陣列本身不是指標。
使用 scores[i] 與 *(scores + i) 可以取得同一個元素。
將陣列傳入函式時,通常需要另外傳入元素數量。

看懂陣列在記憶體中的樣子後,下一篇我們就繼續觀察:為什麼陣列能快速存取元素,但在中間插入或刪除資料時,卻需要大家一起「搬家」。


上一篇
Day-3 陣列:把一群資料整齊地排在一起
下一篇
Day-5 陣列的資料搬家:搜尋、插入與刪除
系列文
30 天資料結構修行:從零開始理解資料結構8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言